#include "dish.h"
#include "chef.h"
#include "judge.h"
#include "set.h"
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

struct Judge_t {
	char *name;
	int nervous;
	JudgeTasteFunction taste;
	Set chefNames;
};

#define RESIGNING 3

//helper function for Set of Chef names
static SetElement copyChef(SetElement chefName){
	char *copidChefName=malloc(strlen(chefName)+1);
	if (copidChefName==NULL) {
		return NULL;
	}
	return strcpy(copidChefName,chefName);
}

//helper function for Set of Chefs names
static void freeChef(SetElement chefName){
	free(chefName);
}

//helper function for Set of Chefs names
static int compareChefs(SetElement chefName1, SetElement chefName2){
	return strcmp(chefName1,chefName2);
}

/**
 * put a result code in a variable
 * @param result Address to put the code in. if NULL, does nothing.
 * @param code The result code to put in *result
 */
static void putResult(JudgeResult *result, JudgeResult code){
	if (result!=NULL){
		*result=code;
	}
}

Judge judgeCreate(const char *name, JudgeTasteFunction taste,
		JudgeResult *result){
	if (name==NULL || taste==NULL){
		putResult(result,JUDGE_INVALID_ARGUMENT);
		return NULL;
	}
	Judge judge=malloc(sizeof(*judge));
	if (judge==NULL){
		putResult(result,JUDGE_OUT_OF_MEMORY);
		return NULL;
	}
	judge->name=malloc(sizeof(name)+1);
	if (judge->name==NULL){
		free(judge);
		putResult(result,JUDGE_OUT_OF_MEMORY);
		return NULL;
	}
	strcpy(judge->name,name);
	judge->chefNames=setCreate(copyChef, freeChef, compareChefs);
	if(judge->chefNames==NULL){
		judgeDestroy(judge);
		putResult(result,JUDGE_OUT_OF_MEMORY);
		return NULL;
	}
	judge->taste=taste;
	judge->nervous=0;
	putResult(result,JUDGE_SUCCESS);
	return judge;
}

Judge judgeCopy(Judge judge, JudgeResult *result){
	if (judge==NULL){
		putResult(result,JUDGE_INVALID_ARGUMENT);
		return NULL;
	}
	Judge copiedJudge=malloc(sizeof(*copiedJudge));
	if(copiedJudge==NULL){
		putResult(result,JUDGE_OUT_OF_MEMORY);
		return NULL;
	}
	copiedJudge->name=malloc(strlen(judge->name)+1);
	if (copiedJudge->name==NULL){
		free(copiedJudge);
		putResult(result,JUDGE_OUT_OF_MEMORY);
		return NULL;
	}
	strcpy(copiedJudge->name,judge->name);
	copiedJudge->chefNames=setCopy(judge->chefNames);
	if (copiedJudge->chefNames==NULL){
		judgeDestroy(copiedJudge);
		putResult(result,JUDGE_OUT_OF_MEMORY);
		return NULL;
	}
	copiedJudge->taste=judge->taste;
	copiedJudge->nervous=judge->nervous;
	putResult(result,JUDGE_SUCCESS);
	return copiedJudge;
}

void judgeDestroy(Judge judge){
	if(judge==NULL){
		return;
	}
	free(judge->name);
	setDestroy(judge->chefNames);
	free(judge);
}

const char *judgeGetName(Judge judge){
	if(judge==NULL){
		return NULL;
	}
	return judge->name;
}

/**
 * helper function for judgeCompareDishes, provides us the chef's name,
 * if the dish is inedible, it inserts the chef to the judge's hated chefs set,
 * @param judge, the tournament's judge.
 * @param dish, the dish.
 * @param chefName, the chef (who has the dish)'s name.
 * @return:
 * JUDGE_OUT_OF_MEMORY if a memory allocation failed.
 * JUDGE_SUCCESS otherwise.
 */
static JudgeResult isDishInedible(Judge judge, char *chefName, Dish dish){
	if (!dishIsEdible(dish)){
		judge->nervous++;
		if(setAdd(judge->chefNames, chefName)==SET_OUT_OF_MEMORY){
			return JUDGE_OUT_OF_MEMORY;
		}
	}
	return JUDGE_SUCCESS;
}

bool judgeCompareDishes(Judge judge, Dish dish1, Chef chef1, Dish dish2,
		Chef chef2, JudgeResult *result){
	if (judge==NULL || dish1==NULL || chef1==NULL || dish2==NULL ||
			chef2==NULL){
		putResult(result,JUDGE_INVALID_ARGUMENT);
		return false;
	}
	char *chef1Name=(char*)chefGetName(chef1);
	char *chef2Name=(char*)chefGetName(chef2);
	if (isDishInedible(judge, chef1Name, dish1)==JUDGE_OUT_OF_MEMORY ||
			isDishInedible(judge, chef2Name, dish2)==JUDGE_OUT_OF_MEMORY){
		putResult(result, JUDGE_OUT_OF_MEMORY);
		return false;
	}
	putResult(result, JUDGE_SUCCESS);
	bool chef1Hated=setIsIn(judge->chefNames,chef1Name);
	bool chef2Hated=setIsIn(judge->chefNames,chef2Name);;
	if(chef1Hated!=chef2Hated){
		return chef2Hated;
	}
	int dishWon=judge->taste(dish1,dish2);
	if (dishWon!=0){
		return (dishWon>0);
	}
	return (strcmp(chef1Name,chef2Name)<0);
}

bool judgeIsResigning(Judge judge){
	if(judge==NULL){
		return false;
	}
	return judge->nervous >= RESIGNING;
}
